package edu.mercer.sse.plainpdf;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;

import com.google.common.collect.Range;
import com.google.common.io.Files;

import static com.google.common.base.Preconditions.*;
import edu.mercer.sse.annotations.Fulfills;
import edu.mercer.sse.error.CriticalException;
import edu.mercer.sse.error.ErrorLoggingVisitor;
import edu.mercer.sse.error.ReturnCodeException;
import edu.mercer.sse.log.LoggerCustom;

/**
 * Convert PDF to TIFF.<p>
 * 
 * Wrapper Facade pattern demonstration.<p>
 * 
 * @see TIFFConversion
 * 
 * @see FileInformation
 * 
 * @see UtilityNomenclature
 */

public class TIFFConverter implements TIFFConversion,
									  UtilityNomenclature
{
	/** 
	 * Name of this utility as defined in configuration. Required for use by 
	 * generic Utility if used with no-arg constructor.<p>
	 * 
	 * @see UtilityNomenclature
	 * @see Utility
	 */
	
	final String UTILITY_KEY = "GS";

	/**
	 * Generic utility instance.<p>
	 * 
	 * Appropriate configuration loaded using {@link #UTILITY_KEY} through 
	 * 	UtilityNomenclature methods.<p>
	 */
	
	private Utility<?> _pdfTiffConversionUtil;
	
	/** Range - valid image resolution setting values */
	
	private final Range<Integer> _resolutionRange = Range.closed(50, 800);

	/** Integer - tiff resolution setting */
	
	private Integer _resolution = 0;
	
	/** String - output file path */
	
	private Path _fileOut = Paths.get("");
	
	
	/**
	 * Constructor.<p>
	 * 
	 * Use defaults for tiff resolution, output file name.<p>
	 * 
	 * @see #TIFFConverter(Integer, String)
	 */
	
	public TIFFConverter()
	{
		super();
		
	}//constructor
	

	/**
	 * Constructor.<p>
	 * 
	 * Specify parameters to underlying utility.<p>
	 * 
	 * tiffResolution argument: equivalent to scanner's resolution; examples: 
	 * 	200, 300,...<p>
	 * 
	 * Default output file is PDF input with extension replaced by 'tiff'<p>
	 * 
	 * @param tiffResolution - Integer, image resolution; example: 300
	 * 
	 * @param fileOut - String, file path / name for output; example: 
	 * 	'myfile.tiff'
	 * 
	 * @see #TIFFConverter()
	 */
	
	public TIFFConverter( final Integer tiffResolution, final String fileOut )
	{
		super();
			
		checkNotNull( tiffResolution, 
				
				"null tiff resolution" );

		checkArgument( _resolutionRange.contains( tiffResolution ), 
				
				"" );
		
		checkNotNull( fileOut, 
				
				"null file out argument" );

		checkArgument( ! fileOut.isEmpty(), 
				
				"empty file out argument" );		
		
		
		_fileOut = Paths.get(fileOut);
		
		if( ! _fileOut.toFile().exists() )
		{
			try
			{
				Files.touch( _fileOut.toFile() );
			
			}
			catch( final IOException err )
			{
				final CriticalException errWrapped = new CriticalException(
						"unable to write to: " + fileOut);
				
				errWrapped.accept(new ErrorLoggingVisitor());
				
				throw errWrapped;
				
			}//try
			
		}//if
		
		
		if( ! _fileOut.toFile().canWrite() )
		{
			final CriticalException err = new CriticalException(
					"unable to write to: " + fileOut);
			
			err.accept(new ErrorLoggingVisitor());
			
			throw err;
			
		}//if

		_resolution = tiffResolution;
		
	}//constructor
	
	
	@Fulfills("TIFFConversion")
	@Override
	public TIFFConversion toTIFF( final FileInformation pdfFile ) 

			throws ReturnCodeException 
	{		
		checkNotNull( pdfFile, "pdf file FileInformation argument is null" );
		
		_pdfTiffConversionUtil = new Utility<>( this );		
		
		try
		{
			
			if( pdfFile.getMimeType().contains("pdf") )
			{
				final String errMessage = "file appears to not be a pdf: " 
						+ pdfFile.getMimeType();

				final CriticalException err = new CriticalException(
						errMessage);

				err.accept(new ErrorLoggingVisitor());

				throw err;

			}//if

			if( ! pdfFile.isAvailable() )
			{
				final CriticalException err = new CriticalException(
						"unable to access: " + pdfFile);

				err.accept(new ErrorLoggingVisitor());

				throw err;			

			}//if

			/* 
			 * set default values for resolution and file-out if not set by 
			 * constructor 
			 */

			if( _fileOut.getFileName() == null || 
					! _resolutionRange.contains( _resolution ) )	
			{
				/* remove 'pdf' extension, replace with 'tiff' */
				
				_fileOut = Paths.get( Files.getNameWithoutExtension( 
						pdfFile.getLocation() ) + ".tiff" );

			}//if

			if( _resolution == 0 ) _resolution = 255;
			
			_pdfTiffConversionUtil.setCommandLineVarValue("FILE_IN", 
					pdfFile.getLocation() );
			
			_pdfTiffConversionUtil.setCommandLineVarValue("TIFF_RESOLUTION", 
					String.valueOf(_resolution) );

			_pdfTiffConversionUtil.setCommandLineVarValue("FILE_OUT", 
					_fileOut.toAbsolutePath().toString() );

			if( ! _pdfTiffConversionUtil.isReady() )
			{
				final CriticalException errWrapped = new CriticalException(
						"pdf to tiff conversion utility wrapper reports not"
						+ " ready: " + pdfFile);

				errWrapped.accept(new ErrorLoggingVisitor());

				throw errWrapped;
				
			}//if
			
			/* run and retrieve, validate return code */
			
			if( ! _pdfTiffConversionUtil._rcRange.contains(
					
					_pdfTiffConversionUtil.asyncInvoke()
										  .get()
										  .getReturnCode() ))
			{
				final ReturnCodeException err = new ReturnCodeException(
						"return code out of range when converting pdf to "
								+ "tiff: " + pdfFile);

				err.accept(new ErrorLoggingVisitor());

				throw err;
				
			}//if
			
			final FileInformation convertedFile = new FileInfo( 
					_fileOut.toString() );
			
			if( !convertedFile.isAvailable() )
			{
				final CriticalException errWrapped = new CriticalException(
						"pdf to tiff conversion reported OK return code for: " 
								+ pdfFile 
								+ ", but file out not OK: " 
								+ _fileOut.toString());

				errWrapped.accept(new ErrorLoggingVisitor());

				throw errWrapped;				
				
			}//if
			
			LoggerCustom.obtainLog("pdf_conversion_to_tiff", 
					Level.FINE).fine("converted to tiff: " + pdfFile );
			
		}
		catch( final ExecutionException | InterruptedException err )
		{
			final CriticalException errWrapped = new CriticalException(
					"error running conversion utility: " + pdfFile);

			errWrapped.accept(new ErrorLoggingVisitor());

			throw errWrapped;
		
		}//try

		
		return this;
	
	}//method

	
	@Fulfills("UtilityNomenclature")	
	@Override
	public String getUtilityKey() 
	{
		return UTILITY_KEY;
		
	}//method


}//class
